#!/usr/bin/env python
#
# Copyright 2013 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A utility to perform basic Wi-Fi testing and network performance testing.

This script needs to be broken up into multiple files to separate the
functionality.  Currently it will scan for wireless networks, attempt to
connect to known networks, and perform a number of tests.

TODO(joeyfreeland): This script is intended to be loaded onto a Raspberry Pi
device running Raspbian and should be accompanied by tespeed.py.
"""
__author__ = "Joey Freeland (joeyfreeland@gmail.com)"

from datetime import datetime
import logging
import time

import device
import network
import ref
import web

CONFIG_FILE = "config.txt"
# TODO(joeyfreeland): Change the software version to an int() and incriment in
# coordination with an update to AppEngine SOFTWARE_VERSION.
SOFTWARE_VERSION = "0.0.7"

logger = logging.basicConfig(level=logging.DEBUG)


def main():
  """String together scanning, configuring, and testing a Wi-Fi network.

  Args:
    None

  Returns:
    None
  """
  start_time = datetime.now()

  # Load a configuration file.
  config = device.LoadConfig(CONFIG_FILE, SOFTWARE_VERSION)

  # Scan for all access points in the area.
  aps, signal_dbm, ssids, best_signal = device.Scan(ref.NETWORKS)

  # Try to connect to every network we know in the area and run some tests.
  for ssid in list(set(ssids).intersection(ref.NETWORKS)):
    results = {}
    results["ssid"] = ssid
    # Check to see if the network requires authentication.  If not, just use
    # iwconfig and run dhclient.  If the network requires WPA, kill any existing
    # wpa_supplicant processes, configure wpa_supplicant, then associate.  Check
    # to see if we associated after running configuration commands. I have to
    # run this up to 3 times because the first attempt never works using an
    # Edimax Wi-Fi dongle.  Super weird.
    # TODO(joeyfreeland): Dig into the Edimax Wi-Fi association issue to
    # understand why Edimax will not associate the first time and provide
    # feedback to Edimax.
    count = 0
    while count < 3:
      device.KillWPA()
      if "8021x" in ref.NETWORKS[ssid].keys():
        device.ConfigureWPA(ref.NETWORKS, ssid)
        device.StartWPA()
        time.sleep(10)
      elif ref.NETWORKS[ssid]["auth"]:
        device.ConfigureWPA(ref.NETWORKS, ssid)
        device.StartWPA()
        device.Associate(ssid)
      else:
        device.Associate(ssid)

      # Verify that we associated to the access point.
      if device.CheckAssociation(ssid) == 1: break
      count += 1

    # Attempt to acquire an IP, gw, and nameservers from the DHCP server.
    device.RequestDHCP(ssid)

    # Verify that we can resolve a name against DHCP provided DNS server.
    results["idns_latency"] = network.CheckDNS("www.google.com")

    # Verify HTTP redirect before authenticating.  If we were redirected,
    # attempt to login.
    if network.CheckRedirect():
      network.CaptivePortalLogin(ref.NETWORKS, ssid)

    # Verify that we can resolve a name against external DNS server.
    results["edns_latency"] = network.CheckDNS("www.google.com", "8.8.8.8")

    # Attempt to load Google, Bing, and Yahoo.  Record success and
    # time.
    results["bing_latency"] = network.LoadSite("http://www.bing.com", "Bing")
    results["google_latency"] = network.LoadSite("http://www.google.com",
                                                 "Google")
    results["yahoo_latency"] = network.LoadSite("http://id.yahoo.com/?p=us",
                                                "Yahoo")

    # Initiate SSH connection to bastion host for remote troubleshooting.
    if config["ssh_host"] and config["ssh_port"] and config["ssh_user"]:
      network.ReverseSshTunnel(config["ssh_host"], config["ssh_port"],
                               config["ssh_user"])

    # Run the NDT client.
    results["ndt_up"], results["ndt_down"] = network.RunNDT()

    # Run the SpeedTest.net latency, upload and download tests via tespeed.py.
    results["local_speedtest_latency"] = (
        network.SpeedTestLatency(config["local_speedtest_name"],
                                 config["local_speedtest_url"]))
    results["local_speedtest_up"], results["local_speedtest_down"] = (
        network.SpeedTestBandwidth(config["local_speedtest_name"],
                                   config["local_speedtest_url"]))
    results["remote_speedtest_latency"] = (
        network.SpeedTestLatency(config["remote_speedtest_name"],
                                 config["remote_speedtest_url"]))
    results["remote_speedtest_up"], results["remote_speedtest_down"] = (
        network.SpeedTestBandwidth(config["remote_speedtest_name"],
                                   config["remote_speedtest_url"]))

    # Check packet loss (ping -c 10?) to (x?). Should this be
    # just the local router, multiple external sites?

    # Figure out the quickest/easiest way to check ports. Should
    # this be nmap to check outgoing ports?  Is there a real simple way to
    # check incoming ports or will we need AppEngine to check back in?

    # Attempt to stream a video using RTSP.
    # results["rtsp_time"] = network.StreamRTSP()

    # Check for the signal strength and speed of this specific access point.
    results["my_channel"], results["my_speed"], results["my_signal"] = (
        device.CheckCurrentWiFiSpecs())

    # Count the number of neighboring devices attached to this AP.
    results["neighbors"] = device.CountNeighbors()

    # Record the external and internal IP address of the device.
    results["external_ip_address"] = network.GetExternalIP()
    results["internal_ip_address"] = device.GetIPConfig()

    # Upload data about SSID to AppEngine.  This should include
    # SSID name, signal strength of the access point we connected to, most
    # common channel, number of access points per channel, etc.

    web.CheckForConfigUpdate(config, CONFIG_FILE)
    config = device.LoadConfig(CONFIG_FILE, SOFTWARE_VERSION)

    # What time were we planning to upload data and how long did it
    # take to run through these tests?
    upload_time = datetime.now()
    duration = upload_time - start_time

    # Set a few last values before uploading results.
    results["mac_address"] = config["mac_address"]
    results["provider"] = config["provider"]
    results["location"] = config["device_name"]
    results["date"] = upload_time.strftime("%Y/%m/%d %H:%M:%S UTC")
    results["duration"] = duration.seconds
    results["aps_per_channel"] = aps
    results["average_signal"] = int(sum(signal_dbm)/len(signal_dbm))
    results["best_signal"] = max(signal_dbm)
    results["ssid_count"] = len(best_signal.keys())
    results["temperature"] = device.GetTemperature()

    web.PostTestResults(results)

    # Check for a software update after all tests are done running.
    web.CheckForSoftwareUpdate(SOFTWARE_VERSION)

    # Sleep for 5 minutes.
    logging.info("Going to sleep for 5 minutes.")
    time.sleep(300)


if __name__ == "__main__":
  # Make sure another process is not still running.
  if device.CheckLock() == 1:
    import sys
    sys.exit()

  while 1:
    main()
